# This file is part of Appartition, a lightweight and portable backup tool.
# Copyright (C) 2012 Simon Grieger
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

###########################################################################
# Package: Appartition
#
# This is the main package, loaded after self-extraction. Most notably, it
# provides the main function. In addition, most program-wide variables are
# declared here.
###########################################################################

package Appartition;

use 5.006;
use strict;
use warnings;

use English '-no_match_vars';

use Appartition::Core;
use Appartition::Shell;
use Appartition::Utilities;

###########################################################################
# Group: Paths
###########################################################################

# String: TMP
#
# This variable stores the path to the temporary directory created during
# self-extraction. It is initialized before <main()> is called.
our $TMP;

# String: LIB
#
# This variable stores the path to the directory where Appartition's
# packages and classes reside. It is initialized before <main()> is called.
our $LIB;

# String: DOC
#
# This variable stores the path to the documentation directory. It is
# initialized before <main()> is called.
our $DOC;

# String: WORKSPACE
#
# This variable stores the path to the workspace directory. It is
# initialized by <set_paths()>, which in turn is called by <main()>.
our $WORKSPACE;

# String: FSTAB
#
# This variable stores the translated path (see
# <Appartition::Utilities::translate_path()> for details) to the actual
# fstab file in use. It is initialized by <set_paths()>, which in turn is
# called by <main()>.
our $FSTAB;

# String: BACKUP
#
# This variable stores the path to the backup directory. It is initialized
# by <set_paths()>, which in turn is called by <main()>.
our $BACKUP;

###########################################################################
# Group: Program-wide Variables
###########################################################################

# ArrayRef: FILE_SYSTEMS
#
# The array referenced by this variable stores all known file systems
# (objects of class <Appartition::FileSystem>). New file systems are pushed
# onto the end of the array. The array never shrinks; deleting a file
# system from the list is accomplished by setting the corresponding entry
# to undef. This way, each file system gets its unique ID (i.e., the file
# system's index in this array), starting at zero. The IDs never change,
# and are never used more than once.
#
# Initially, this variable references an empty array. This array will be
# filled as soon as the workspace is prepared.
#
# Note that the list of known file systems should not be accessed or
# modified directly; use the dedicated functions in <Appartition::Core>
# instead.
our $FILE_SYSTEMS = [];

# Array: OPTION_SPECS
#
# This array stores information on all available command line options. Each
# of the array's elements is a string, specifying an option in a format
# suitable for use by the perl module Getopt::Long. Thus, the array can be
# fed directly into Getopt::Long's GetOptions() as the option
# specifications, accompanied by <OPTION_VALUES> to store the options'
# values.
#
# See the user's documentation for a list of available options. More
# options might be available in future versions.
our @OPTION_SPECS =
  (
   'batch|b=s@',
   'chroot-command|C=s',
   'command|e=s@',
   'config|c=s',
   'create-mount-points|p',
   'debug|D',
   'detailed-rsync|i',
   'dry-run|d',
   'empty-workspace|E',
   'fstab|f=s',
   'help|h|?',
   'ignore-boot|B',
   'include-noauto|n',
   'mount-command|m=s',
   'mount-options|o=s',
   'root|r=s',
   'rsync-command|R=s',
   'rsync-options|O=s',
   'umount-command|u=s',
   'usage|U',
   'version|V',
   'workspace|w=s',
  );

# HashRef: OPTION_VALUES
#
# The hash referenced by this variable stores all options' values. Each key
# represents one option, initialized with a default value. The keys and
# their values are described below. See the user's documentation for their
# actual meaning. More keys and values might be added in future versions.
#
# Keys and Values:
#
# batch               - Array reference, default: empty array.
# chroot-command      - String, default: 'chroot'.
# command             - Array reference, default: empty array.
# config              - String, default: undef (none specified).
# create-mount-points - Boolean, default: false.
# debug               - Boolean, default: false.
# detailed-rsync      - Boolean, default: false.
# dry-run             - Boolean, default: false.
# empty-workspace     - Boolean, default: false.
# fstab               - String, default: undef (none specified).
# help                - Boolean, default: false.
# ignore-boot         - Boolean, default: false.
# include-noauto      - Boolean, default: false.
# mount-command       - String, default: 'mount'.
# mount-options       - String, default: 'noatime,nodiratime'.
# root                - String, default: undef (none specified).
# rsync-command       - String, default: 'rsync'.
# rsync-options       - String, default: undef.
# umount-command      - String, default: 'umount'.
# usage               - Boolean, default: false.
# version             - Boolean, default: false.
# workspace           - String, default: undef (none specified).
our $OPTION_VALUES =
  {
   'batch',              => [],
   'chroot-command'      => 'chroot',
   'command'             => [],
   'config'              => undef,
   'create-mount-points' => 0,
   'debug'               => 0,
   'detailed-rsync'      => 0,
   'dry-run'             => 0,
   'empty-workspace'     => 0,
   'fstab'               => undef,
   'help'                => 0,
   'ignore-boot'         => 0,
   'include-noauto'      => 0,
   'mount-command'       => 'mount',
   'mount-options'       => 'noatime,nodiratime',
   'root'                => undef,
   'rsync-command'       => 'rsync',
   'rsync-options'       => undef,
   'umount-command'      => 'umount',
   'usage'               => 0,
   'version'             => 0,
   'workspace'           => undef,
  };

# Array: CONFLICTING_MOUNT_OPTIONS
#
# This array stores information on conflicting mount options. Each of the
# array's entries itself is a reference to an array holding a group of
# mount options, of which only one is allowed to be set at a time. Note
# that negated mount options (e.g., "noatime", which is the negation of
# "atime") are handled by
# <Appartition::Utilities::get_conflicting_mount_options()>, and thus not
# included in this array.
#
# The array is initialized with reasonable default values. It might be
# incomplete, though, and therefore extended in future versions.
our @CONFLICTING_MOUNT_OPTIONS =
  (
   ['ro', 'rw'],
  );

# Array: IGNORED_FS_TYPES
#
# This array stores a list of file system types. File systems of one of the
# types in this list will not be selected for backups.
#
# The array is initalized with reasonable default values. It might be
# extended in future versions.
our @IGNORED_FS_TYPES = qw'autofs cgroup debugfs devpts devtmpfs fusectl
                           hugetlbfs mqueue proc ramfs securityfs selinuxfs
                           swap sysfs tmpfs usbdevfs usbfs';

###########################################################################
# Group: High-level Functions
###########################################################################

# Function: main
#
# Runs the program. This is the main function, which is called after
# self-extraction.
#
# Returns:
#
#   Zero on success, a value greater than zero on failure.
sub main #()
  {
    local $@;

    install_signal_handlers();

    Appartition::Shell::say_hello();
    Appartition::Shell::parse_command_line();

    set_paths();

    eval {
      Appartition::Core::initialize();
    };

    Appartition::Shell::show_error($@) if ($@);

    Appartition::Core::deinitialize();
    Appartition::Shell::say_goodbye(not $@);
  }

###########################################################################
# Group: Helper Functions
###########################################################################

# Function: install_signal_handlers
#
# Installs all the necessary signal handlers.
#
# Returns:
#
#   True.
sub install_signal_handlers #()
  {
    # TODO: Add handlers for SIGINT, SIGQUIT, SIGKILL, and others.

    my %SIGNAL_HANDLERS =
      (
       '__DIE__'  => \&handle_die,
       '__WARN__' => \&handle_warn,
      );

    foreach my $signal (keys %SIGNAL_HANDLERS) {
      $SIG{$signal} = $SIGNAL_HANDLERS{$signal};
    }

    return 1;
  }

# Function: set_paths
#
# Sets all paths that haven't already been set during self-extraction. This
# function must not be called before processing the options on the command
# line.
#
# Returns:
#
#   True.
sub set_paths #()
  {
    $WORKSPACE = Appartition::Utilities::strip_trailing_slashes(defined $OPTION_VALUES->{'workspace'} ?
                                                                $OPTION_VALUES->{'workspace'} : $TMP . '/workspace');
    $FSTAB = Appartition::Utilities::translate_path(defined $OPTION_VALUES->{'fstab'} ?
                                                    $OPTION_VALUES->{'fstab'} : '/etc/fstab');
    $BACKUP = Appartition::Utilities::strip_trailing_slashes($ARGV[0]);

    return 1;
  }

###########################################################################
# Group: Signal Handlers
#
# The functions in this group are signal handlers and thus not meant to be
# called directly. They are installed by <install_signal_handlers()>.
#
# Each function expects parameters depending on the signal it handles. A
# signal handler's return value is usually ignored; that is why the
# functions do not return anything meaningful. Some of them do not even
# return at all.
###########################################################################

# Function: handle_die
#
# Handles calls to die().
sub handle_die
  {
    die @_ if ($EXCEPTIONS_BEING_CAUGHT);
    die Appartition::Shell::format_output('Error: ', @_);
  }

# Function: handle_warn
#
# Handles calls to warn().
sub handle_warn
  {
    warn Appartition::Shell::format_output('Warning: ', @_);
  }

1;
